88c97a
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014-2015 the original author or authors.
+ * Copyright 2014-2016 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,8 +16,9 @@
 package org.springframework.data.rest.core.config;
 
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
-import java.util.Map.Entry;
+import java.util.Set;
 
 import org.springframework.core.annotation.AnnotationUtils;
 import org.springframework.data.rest.core.projection.ProjectionDefinitions;
@@ -35,14 +36,14 @@
public class ProjectionDefinitionConfiguration implements ProjectionDefinitions
 	private static final String PROJECTION_ANNOTATION_NOT_FOUND = "Projection annotation not found on %s! Either add the annotation or hand source type to the registration manually!";
 	private static final String DEFAULT_PROJECTION_PARAMETER_NAME = "projection";
 
-	private final Map<ProjectionDefinitionKey, Class<?>> projectionDefinitions;
+	private final Set<ProjectionDefinition> projectionDefinitions;
 	private String parameterName = DEFAULT_PROJECTION_PARAMETER_NAME;
 
 	/**
 	 * Creates a new {@link ProjectionDefinitionConfiguration}.
 	 */
 	public ProjectionDefinitionConfiguration() {
-		this.projectionDefinitions = new HashMap<ProjectionDefinitionKey, Class<?>>();
+		this.projectionDefinitions = new HashSet<ProjectionDefinition>();
 	}
 
 	/*
@@ -117,7 +118,7 @@
public class ProjectionDefinitionConfiguration implements ProjectionDefinitions
 		Assert.notEmpty(sourceTypes, "Source types must not be null!");
 
 		for (Class<?> sourceType : sourceTypes) {
-			this.projectionDefinitions.put(new ProjectionDefinitionKey(sourceType, name), projectionType);
+			this.projectionDefinitions.add(new ProjectionDefinition(sourceType, projectionType, name));
 		}
 
 		return this;
@@ -139,8 +140,8 @@
public class ProjectionDefinitionConfiguration implements ProjectionDefinitions
 	@Override
 	public boolean hasProjectionFor(Class<?> sourceType) {
 
-		for (ProjectionDefinitionKey key : projectionDefinitions.keySet()) {
-			if (key.sourceType.isAssignableFrom(sourceType)) {
+		for (ProjectionDefinition definition : projectionDefinitions) {
+			if (definition.sourceType.isAssignableFrom(sourceType)) {
 				return true;
 			}
 		}
@@ -159,39 +160,55 @@
public class ProjectionDefinitionConfiguration implements ProjectionDefinitions
 		Assert.notNull(sourceType, "Source type must not be null!");
 
 		Class<?> userType = ClassUtils.getUserClass(sourceType);
+		Map<String, ProjectionDefinition> byName = new HashMap<String, ProjectionDefinition>();
 		Map<String, Class<?>> result = new HashMap<String, Class<?>>();
 
-		for (Entry<ProjectionDefinitionKey, Class<?>> entry : projectionDefinitions.entrySet()) {
-			if (entry.getKey().sourceType.isAssignableFrom(userType)) {
-				result.put(entry.getKey().name, entry.getValue());
+		for (ProjectionDefinition entry : projectionDefinitions) {
+
+			if (!entry.sourceType.isAssignableFrom(userType)) {
+				continue;
+			}
+
+			ProjectionDefinition existing = byName.get(entry.name);
+
+			if (existing == null || isSubTypeOf(entry.sourceType, existing.sourceType)) {
+				byName.put(entry.name, entry);
+				result.put(entry.name, entry.targetType);
 			}
 		}
 
 		return result;
 	}
 
+	private static boolean isSubTypeOf(Class<?> left, Class<?> right) {
+		return right.isAssignableFrom(left) && !left.equals(right);
+	}
+
 	/**
 	 * Value object to define lookup keys for projections.
 	 * 
 	 * @author Oliver Gierke
 	 */
-	static final class ProjectionDefinitionKey {
+	static final class ProjectionDefinition {
 
-		private final Class<?> sourceType;
+		private final Class<?> sourceType, targetType;
 		private final String name;
 
 		/**
 		 * Creates a new {@link ProjectionDefinitionKey} for the given source type and name;
 		 * 
 		 * @param sourceType must not be {@literal null}.
+		 * @param targetType must not be {@literal null}.
 		 * @param name must not be {@literal null} or empty.
 		 */
-		public ProjectionDefinitionKey(Class<?> sourceType, String name) {
+		public ProjectionDefinition(Class<?> sourceType, Class<?> targetType, String name) {
 
 			Assert.notNull(sourceType, "Source type must not be null!");
+			Assert.notNull(targetType, "Target type must not be null!");
 			Assert.hasText(name, "Name must not be null or empty!");
 
 			this.sourceType = sourceType;
+			this.targetType = targetType;
 			this.name = name;
 		}
 
@@ -202,12 +219,13 @@
public class ProjectionDefinitionConfiguration implements ProjectionDefinitions
 		@Override
 		public boolean equals(Object obj) {
 
-			if (!(obj instanceof ProjectionDefinitionKey)) {
+			if (!(obj instanceof ProjectionDefinition)) {
 				return false;
 			}
 
-			ProjectionDefinitionKey that = (ProjectionDefinitionKey) obj;
-			return this.name.equals(that.name) && this.sourceType.equals(that.sourceType);
+			ProjectionDefinition that = (ProjectionDefinition) obj;
+			return this.name.equals(that.name) && this.sourceType.equals(that.sourceType)
+					&& this.sourceType.equals(that.sourceType);
 		}
 
 		/* 
@@ -221,6 +239,7 @@
public class ProjectionDefinitionConfiguration implements ProjectionDefinitions
 
 			result += name.hashCode();
 			result += sourceType.hashCode();
+			result += targetType.hashCode();
 
 			return result;
 		}
